# -*- coding: utf-8 -*-
from d115.utils.mathutils import dcml, randint
from d115.utils.listutils import map0_n, ego
from d115.dateutil import parser, relativedelta
import datetime, calendar
import re

date = datetime.date
MIN_DATE = date(datetime.MINYEAR, 1, 1)
MAX_DATE = date(datetime.MAXYEAR, 12, 31)
now = datetime.datetime.now
bg_now = lambda :datetime.datetime.now().strftime("%d.%m.%Y")
bg_prev_month = lambda: datetime.datetime(datetime.datetime.now().year,datetime.datetime.now().month - 1,1) if datetime.datetime.now().month > 1 else datetime.datetime(datetime.datetime.now().year - 1,12,1) #the first day of the previous month
bg_pm_days = lambda: calendar.monthrange(bg_prev_month().year,bg_prev_month().month)[1]
bg_pm_fd = lambda: bg_prev_month().strftime("%d.%m.%Y") #calculates the first day of the previous month
bg_pm_ld = lambda: datetime.datetime(bg_prev_month().year,bg_prev_month().month,bg_pm_days()).strftime("%d.%m.%Y") #calculates the last day of the previous month
today = date.today
reldelta = relativedelta.relativedelta
mdelta = lambda s,e: reldelta(s,e).years*12+reldelta(s,e).months
reldays = lambda d: reldelta(days=d)
one_day = reldays(1)
relmonths = lambda m: reldelta(months=m)
one_month = relmonths(1)
relyears = lambda y: reldelta(years=y)
one_year = relyears(1)
bg2datetime = lambda d: parser.parse(d, dayfirst=True, fuzzy=True)
bg2date = lambda d: bg2datetime(d).date()
dateratio = lambda a,b,c,d: (b-a).days / dcml((d-c).days)

def get_timestamp():
    return "".join(str(t) for t in now().timetuple() if t>=0)

def is_leap(year):
    if year % 400 == 0:
        return True
    elif year % 100 == 0:
        return False
    elif year % 4 == 0:
        return True
    else:
        return False

def leaps(s,e):
    """returns number of leap days in a period"""
    if s >= date(s.year, 2, 28):
        s = date(s.year + 1,1,1)
    if e <= date(e.year, 2, 28):
        e -= date(e.year - 1, 1,1)

    total = 0
    
    for y in range(s.year, e.year + 1):
        if is_leap(y):
            total += 1
    
    return total

def parse_xs_date(xs_date):
    year, month, day = map(int, xs_date.split("-"))
    return datetime.date(year, month, day).strftime("%d.%m.%Y")

def overlap_ratio(a, b, c, d):
    if (b <= c)  or (a >= d): return 0

    return smart_date_ratio(max(a,c), min(b,d), c, d) 

def split_period_(t, s, e, f=False):
    while s < e:
        yield s
        s = (s + relmonths(12/t)) if t else MAX_DATE

def split_period_by_count(count, t, s,e, f=False):
    for i in range(count):

        yield s + (relmonths(12/t * i))

split_period = lambda t, s, e, f=False: map(start_month if f else ego, split_period_(t, s, e))

def smart_date_ratio(a,b,c,d):
    """
    l1, l2 = leaps(a,b), leaps(c,d) #remove leap days

    b += reldays(-l1) 
    d += reldays(-l2 - reldelta(d,c).years)
    """

    r1 = reldelta(b,a)
    r2 = reldelta(d,c)

    if not any([r1.days, r1.months, r2.days, r2.months]):
        return r1.years / dcml(r2.years)
    if not any([r1.days, r2.days]):
        return (r1.years*12+r1.months) / dcml(r2.years*12+r2.months)
    return dateratio(a,b,c,d)

def date2bg(dt_obj, format = None):
    """ Formats a date object in BG date string.  """
    if isinstance(dt_obj, datetime.datetime):
        return dt_obj.strftime(format if format else "%d.%m.%Y %H:%M")
    elif isinstance(dt_obj, date):
        return dt_obj.strftime(format if format else "%d.%m.%Y")
    return dt_obj

def egn2bday(egn):
    year, month, day = map(int, [egn[:2],egn[2:4],egn[4:6]])
    if month > 40:
        month -= 40
        year += 2000
    elif month > 20:
        month -= 20
        year += 1800
    else:
        year += 1900
    try:
        return date(year, month, day)
    except ValueError:
        raise ValueError('day is out of range for month, %d.%d.%d! Given egn was %s' % (day, month, year, egn))

egn2age = lambda e, as_of=None: reldelta(as_of or today(), egn2bday(e)).years
egn2age_exact =  lambda e, as_of=None: reldelta(as_of or today(), egn2bday(e))
dob2age_exact =  lambda e, as_of=None: reldelta(as_of or today(), e)

def week_of(dt_obj):
    wday = dt_obj.weekday()
    return map0_n(lambda i: dt_obj+reldays(i-wday), 7)
start_week = lambda x: week_of(x)[0]
end_week = lambda x: week_of(x)[-1]
week_range = lambda x: (start_week(x), end_week(x))
month_length = lambda x: calendar.monthrange(x.year, x.month)[1]
start_month = lambda x: date(x.year, x.month, 1)
end_month = lambda x: date(x.year, x.month, month_length(x))
month_of = lambda d: map0_n(lambda i:start_month(d)+reldays(i),month_length(d))
month_range = lambda x: (start_month(x), end_month(x))
date_range = lambda s,e: map0_n(lambda i: s+reldays(i), (e-s).days+1)
add_to_date = lambda s,y,m,d: s+reldelta(years=y, months=m, days=d)
reldelta_tuple = lambda rd: (rd.years, rd.months, rd.days)
date_delta = lambda s,e: reldelta_tuple(reldelta(e, s))

week_days = {1: u"Понеделник",
             2: u"Вторник",
             3: u"Сряда",
             4: u"Четвъртък",
             5: u"Петък",
             6: u"Събота",
             7: u"Неделя",
             }
date2bgweekd = lambda x: week_days[x.weekday()]
    
def calculate_age(born, as_of = None):
    """
        Calculate the age of a person with date of birth
    """
    
    as_of = as_of or today()
    try:
        birthday = date(today().year, born.month, born.day)
    except ValueError:
        # Raised when person was born on 29 February and the current
        # year is not a leap year.
        birthday = date(today().year, born.month, born.day - 1)
    if birthday > today():
        return today().year - born.year - 1
    else:
        return today().year - born.year
    
def random_date(start_date=MIN_DATE, end_date=MAX_DATE):
    if start_date > end_date:
        return False
    year = randint(start_date.year, end_date.year)
    mo_low = 1
    mo_high = 12
    if year == end_date.year:
        mo_high = end_date.month
    if year == start_date.year:
        mo_low = start_date.month
    month = randint(mo_low, mo_high)
    day_low = 1
    day_high = calendar.monthrange(year, month)[1]
    if (year==end_date.year) and (month==end_date.month): 
        day_high = end_date.day
    if (year==start_date.year) and (month==start_date.month): 
        day_low = start_date.day
    day = randint(day_low, day_high)
    return date(year, month, day)

def add_mins_to_time(timeval, mins_to_add):
    dummy_date = datetime.date(1, 1, 1)
    full_datetime = datetime.datetime.combine(dummy_date, timeval)
    added_datetime = full_datetime + datetime.timedelta(minutes = mins_to_add)
    return added_datetime.time()
